![]() |
![]() |
|
Damit befindet sich das Formular zwar im Hauptspeicher, auf dem Bildschirm erscheint es aber noch nicht. Die Komponenten der Form sind zu diesem Zeitpunkt allerdings bereits initialisiert. Zur Anzeige muss noch die Methode Show der Form aufgerufen werden:
Das Gegenstück zu Show ist Hide. Nach dem Aufruf dieser Methode ist die Form zwar nicht mehr sichtbar, bleibt aber weiter im Hauptspeicher und kann ohne Neuinstanziierung mit Show erneut angezeigt werden.
Dasselbe Ergebnis erreichen Sie auch durch das Setzen der Eigenschaft Visible der Form auf true bzw. false. Zum Schließen und Entladen einer Form dient die Methode Close:
Alle drei Methodenaufrufe haben das Auslösen einer Ereigniskette zur Folge, wie sie weiter oben beschrieben ist. 15.8.3 Mehrere Fenster verwalten
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| static void Main() { |
| Application.Run(new Form1()); |
| } |
Run richtet eine Nachrichtenschleife für das Fenster (Startfenster) ein, dessen Referenz übergeben wird. Die Nachrichtenschleife wird geschlossen, wenn die als Argument übergebene Form entladen wird, denn ein Automatismus fügt dem FormClosed-Ereignis der Form implizit einen Ereignishandler hinzu, der seinerseits Application.ExitThread aufruft. Mit ExitThread wird der aktuelle Thread beendet und damit auch die Laufzeit der Anwendung.
Dieses Verhalten hat weitreichende Konsequenzen. Wird nämlich zur Laufzeit einer Anwendung ein weiteres Fenster mit
| Form2 frm = new Form2(); |
| frm.Show(); |
instanziiert und angezeigt, gliedert es sich in die laufende Nachrichtenschleife ein und teilt sich diese mit dem Startfenster. Das bedeutet aber nicht, dass das neue Fenster gleichberechtigt ist. Vielmehr hat nur das Hauptfenster die Verantwortung darüber, wann die Nachrichtenschleife geschlossen wird – nämlich genau dann, wenn es selbst geschlossen wird. Die Folge ist, dass jedes Fenster der Anwendung geschlossen wird, das zu dieser Nachrichtenschleife gehört. Mit anderen Worten formuliert:
| Jede Form-Instanz einer Anwendung, die nicht als Argument dem Aufruf der Application.Run-Methode übergeben wurde, ist abhängig von der Lebensdauer des Startfensters. |
Dieses Verhalten ist nicht immer wünschenswert. Wenn Sie auf den Automatismus der Nachrichtenschleife verzichten wollen, müssen Sie deren Steuerung selbst in die Hand nehmen. Dabei genügt es nicht, zuerst das Startformular zu instanziieren und danach mit der Methode Run eine Nachrichtenschleife einzurichten:
| Form1 frm = new Form1(); |
| frm.Show(); |
| Application.Run(); |
Bei dieser Lösung sind Nachrichtenschleife und Startfenster nicht aneinander gekoppelt, aber es fehlt definitiv eine Anweisung, um die Nachrichtenschleife zu beenden. Die Anwendung läuft auch nach dem Schließen des letzten Fensters unbemerkt im Hintergrund weiter. Das können Sie sehr schön nach dem Start der Anwendung aus der Entwicklungsumgebung heraus erkennen: das Visual Studio schaltet nicht in den Entwicklungsmodus zurück.
Prinzipiell ist die Lösung des Problems trivial, denn die Nachrichtenschleife muss genau dann mit Application.Exit bzw. Application.ExitThread beendet werden, wenn das letzte Fenster geschlossen wird. Es stellt sich damit die Frage: Wie können wir feststellen, wann das letzte Fenster geschlossen wird?
Zu den sinnvollsten Erweiterungen, die das .NET Framework 2.0 aufzuweisen hat, gehört die Klasse FormCollection. Jede Form, die geöffnet wird, trägt sich in eine Instanz dieser Klasse ein, die jede Windows-Anwendung automatisch bereitstellt. Es gibt keine Methoden in FormCollection, um die enthaltenen Forms zu verwalten. Sie werden also die üblichen Methoden Add, Remove usw. vermissen. Über Count lässt sich lediglich die Anzahl der enthaltenen Forms ermitteln, und über den Indexer können Sie auf die einzelnen Forms zugreifen.
Die Referenz auf die interne Forms-Auflistung liefert die statische Eigenschaft OpenForms der Klasse Application. Damit ist die Lösung unseres Problems aus dem letzten Abschnitt vorgezeichnet. Zur Erinnerung: Wir hatten die feste Bindung zwischen dem Startfenster und der Nachrichtenschleife durch Aufruf der parameterlosen Überladung von Application.Run aufgehoben und mussten nur noch einen Mechanismus finden, um nach dem Schließen der letzten Form auch die Nachrichtenschleife zu beenden.
Jetzt ist der Rest sehr einfach. Im Ereignis FormClosed prüfen wir, ob mit der aktuellen Form auch die letzte der laufenden Anwendung geschlossen wird, der Zähler des FormCollection-Objekts also 0 liefert. In diesem Fall wird entweder die Methode Exit oder ExitThread von Application aufgerufen, die bekanntlich auch der Nachrichtenschleife ein Ende setzen.
| private void Form1_FormClosed(object sender, FormClosedEventArgs e) { |
| if (Application.OpenForms.Count == 0) |
| Application.Exit(); |
| } |
Diese Anweisungen müssen im FormClosed-Ereignishandler codiert werden, da der Zähler zwischen den beiden Ereignissen FormClosing und FormClosed herabgesetzt wird.
Application.OpenForm liefert über den Indexer die Referenz auf eine geöffnete Form. Dazu können Sie entweder einen Integer oder einen String übergeben. Der Integer beschreibt die Position in der nullbasierten Auflistung. Meistens werden Sie damit jedoch nicht viel Erfolg haben, weil Sie in der Regel die Position des Fensters in der Collection nicht kennen. Besser hingegen ist die Übergabe einer Zeichenfolge. Diese beschreibt den Titelleistentext der gesuchten Form.
Um ein bestimmtes Fenster in der Auflistung mittels des Titelleistentextes wiederzufinden, müssen Sie eigentlich nur noch dessen Eindeutigkeit gewährleisten. Im folgenden Beispiel wird das gezeigt. Das Programm enthält zwei Forms: Die erste beschreibt das Startfenster, die zweite ein Dokumentenfenster. Das Dokumentenfenster hat eine Textbox, die den gesamten Arbeitsbereich der Form für sich beansprucht. Hier kann der Anwender einen beliebigen Text eingeben.
Ein Dokumentenfenster wird aus dem Startfenster heraus geöffnet. Es können sogar beliebig viele sein. Jedes Dokumentenfenster trägt seinen Titelleistentext in eine Listbox (Name = lstDocuments) der Hauptform ein und auch wieder aus, wenn es geschlossen wird. Wenn der Anwender in der Listbox ein Dokument selektiert und anschließend auf eine Schaltfläche klickt, wird der Inhalt des Dokumentenfensters von einer Textbox im Hauptfenster übernommen.

Hier klicken, um das Bild zu vergrößern
Abbildung 15.14 Die Fenster des Beispiels »FormCollection«
Sehen wir uns zuerst den Code des Dokumentenfensters sowie die Klasse Program an.
| // ------------------------------------------------------------- |
| // Beispiel: ...\Kapitel 15\FormCollection |
| // ------------------------------------------------------------- |
| public partial class Document : Form { |
| public Document() { |
| InitializeComponent(); |
| // Titelleiste festlegen |
| this.Text = "Document" + Program.DocCounter; |
| Program.DocCounter++; |
| // Titelleistentext zur Listbox des Hauptfensters hinzufügen |
| ((Form1)Application.OpenForms[0]).lstDocuments.Items.Add(this.Text); |
| } |
| private void Document_FormClosed(object sender, FormClosedEventArgs e) { |
| // Titelleistentext aus der Listbox des Hauptfensters löschen |
| ((Form1)Application.OpenForms[0]).lstDocuments.Items.Remove( |
| this.Text); |
| } |
| } |
| static class Program { |
| public static int DocCounter = 1; |
| static void Main() { |
| Application.EnableVisualStyles(); |
| Application.Run(new Form1()); |
| } |
| } |
Im Konstruktor wird nach dem Ausführen von InitializeComponente die eindeutige Beschriftung der Titelleiste des Objekts gewährleistet. Dazu ist in der Klasse Program, die bekanntermaßen auch Main bereitstellt, die statische Variable DocCounter deklariert.
Anschließend wird der Titelleistentext in die Listbox des Hauptfensters eingetragen. Die Referenz auf das Startfenster liefert der erste FormCollection-Eintrag (Index = 0). Die zurückgelieferte Referenz muss noch in den passenden Typ (Form1) konvertiert werden, bevor der Titelleistentext der Listbox (Name = lstDocuments) übergeben werden kann. Beachten Sie, dass die Deklaration der Listbox zumindest internal sein muss. Auf den Code, der direkt mit der ListBox zusammenhängt, gehen wir an dieser Stelle nicht ein. Vergessen werden darf nicht, den Zähler DocCounter zu erhöhen.
Aus der FormCollection trägt sich das Dokumentenfenster automatisch aus, wenn es geschlossen wird. Die Listbox des Hauptfensters wird davon aber nichts mitbekommen und muss eine entsprechende Benachrichtigung erhalten. Diese wird bei Auslösung des Ereignisses FormClosed verschickt.
Jetzt folgt noch der Code des Hauptfensters.
| public partial class Form1 : Form { |
| private void btnOpenDocument_Click(object sender, EventArgs e) { |
| // neues Dokumentenfenster öffnen |
| Document frm = new Document(); |
| frm.Show(); |
| } |
| private void btnGetText_Click(object sender, EventArgs e) { |
| // Inhalt des in der Listbox ausgewählten Dokumentenfensters übernehmen |
| try { |
| int index = this.lstDocuments.SelectedIndex + 1; |
| this.txtDocumentContent.Text = |
| ((Document)Application.OpenForms[index]).txtText.Text; |
| } |
| catch (InvalidCastException ex) { |
| MessageBox.Show("Sie haben kein Dokument ausgewählt.", "Fehler"); |
| } |
| catch (Exception ex) { |
| MessageBox.Show(ex.Message, "Fehler"); |
| } |
| } |
| private void btnBeenden_Click(object sender, EventArgs e) { |
| Application.Exit(); |
| } |
| } |
Die beiden Ereignishandler, welche die Click-Ereignisse der mit Dokument öffnen und Beenden beschrifteten Schaltflächen behandeln, bedürfen keiner weiteren Erläuterung.
Etwas genauer sehen wir uns aber den Ereignishandler btnGetText_Click an. Zur Laufzeit soll der Anwender ein Dokument in der Listbox auswählen. Alle Elemente in einer Listbox werden von einer Collection verwaltet, so dass wir nur noch den Index des selektierten Elements bestimmen müssen. Dieser wird in der lokalen Variablen index gespeichert. index übergeben wir dem Indexer von Application.OpenForms und haben nach entsprechender Konvertierung den Zugriff auf die Textbox des Dokumentenfensters. (Hinweis: Auch die Textbox des Dokumentenfensters muss internal oder public deklariert sein).
Anwender machen nie das, was sie sollen oder dürfen. Das müssen wir abschließend noch berücksichtigen. Wählt der Benutzer keinen Listboxeintrag aus, wäre eine Ausnahme die Folge. Daher wird der Code des Ereignishandlers in einen try-Block gefasst.
Sie können auch für jede Form eine eigene Nachrichtenschleife vorsehen. Da eine Nachrichtenschleife an einen Thread gebunden ist, bedeutet das, dass Sie für das entsprechende Fenster einen weiteren Thread starten müssen. Im folgenden Beispielprogramm sehen Sie das prinzipielle Vorgehen.
| // -------------------------------------------------------------- |
| // Beispiel: ...\Kapitel 15\FormMultithreading |
| // -------------------------------------------------------------- |
| ... |
| public partial class Form1 : Form { |
| private void btnNewForm_Click(object sender, EventArgs e) { |
| Thread thread = new Thread(new ThreadStart(startNewForm)); |
| thread.Start(); |
| } |
| // neues Fenster in einem eigenen Thread starten |
| private void startNewForm() { |
| Application.Run(new Form2()); |
| } |
| } |
Mit dem Aufruf von Main wird wie üblich die Anwendung gestartet und das Startfenster als Argument der Run-Methode übergeben. Damit werden ein primärer Thread sowie eine Nachrichtenschleife gestartet, die beide erst dann beendet werden, wenn das Startfenster geschlossen wird.
Jedes weitere Fenster soll jetzt in einer eigenen Nachrichtenschleife gestartet werden. Im Ereignishandler einer Schaltfläche wird dazu zuerst eine neue Thread-Instanz erzeugt. Dieser wird die Adresse der Methode übergeben, die beim Start des Threads aufgerufen werden soll (im Beispiel handelt es sich um startNewForm). Anschließend wird der neue Thread mit Start ins Leben gerufen:
| Thread thread = new Thread(new ThreadStart(startNewForm)); |
| thread.Start(); |
Die Methode startNewForm enthält nur eine Anweisung:
| Application.Run(new Form2()); |
Die Folge des Aufrufs ist nicht nur ein neuer Thread, sondern gleichzeitig auch eine zweite, von der ersten vollkommen unabhängige Nachrichtenschleife. Wird der Ereignishandler mehrfach ausgeführt, erhöht sich die Anzahl der Fenster und gleichzeitig auch die Anzahl der laufenden Threads.
Soll die Anwendung unabhängig von der Anzahl der geöffneten Fenster sofort beendet werden, ist die statische Methode Exit der Klasse Application passend. Während ExitThread nur den aktuellen Thread zerstört, werden mit Exit alle Threads beendet und infolgedessen auch die Laufzeit der Anwendung. ExitThread führt nur dann zum Beenden der Anwendung, wenn es sich um den letzten Thread handelt.
Manchmal nimmt die Initialisierung einer Anwendung eine längere Zeitspanne in Anspruch. Um den Anwender nicht darüber im Unklaren zu lassen, ob das Programm tatsächlich startet oder nicht, wird während dieser Phase informationshalber ein Fenster angezeigt, das als Splash-Fenster bezeichnet wird. Splash-Fenster haben keine Titelleiste und reagieren im Allgemeinen nicht auf Ereignisse. Daher ist es nicht notwendig, das Fenster einer Nachrichtenschleife zuzuordnen, und wir können uns in Main eine Instanz der Splash-Form besorgen und anzeigen lassen, bevor die Nachrichtenschleife ins Leben gerufen wird.
Um ein Fenster ohne Titelleiste zu erzeugen, darf die Eigenschaft Text der Form keinen Eintrag haben, und ControlBox muss auf false eingestellt sein. Die Anzeigeposition ist üblicherweise mittig auf dem Bildschirm, StartPosition ist also FormStartPosition.CenterScreen.
Das Beispielprogramm SplashDemo hat einen solchen SplashScreen (Abbildung 15.15).

Hier klicken, um das Bild zu vergrößern
Abbildung 15.15 Typisches Splash-Fenster
| // ------------------------------------------------------------- |
| // Beispiel: ...\Kapitel 15\SplashDemo |
| // ------------------------------------------------------------- |
| public class Program { |
| static void Main() { |
| Application.EnableVisualStyles(); |
| Splash frmSplash = new Splash(); |
| frmSplash.Show(); |
| Application.DoEvents(); |
| try { |
| for(int i = 0; i < 100; i++) { |
| // Initialisierungsarbeiten |
| frmSplash.label2.Text = "Initialisierung: ... " + i; |
| frmSplash.label2.Refresh(); |
| // throw-Anweisung dient nur zum Testen |
| // throw new Exception(); |
| Thread.Sleep(50); |
| } |
| } |
| catch { |
| MessageBox.Show("Beim Starten ist ein Fehler aufgetreten.", |
| Application.ProductName, MessageBoxButtons.OK, |
| MessageBoxIcon.Error); |
| Application.Exit(); |
| } |
| frmSplash.Close(); |
| Application.Run(new Form1()); |
| } |
| } |
Damit die Initialisierungsarbeiten, die im Beispiel durch eine for-Schleife simuliert werden, nicht beginnen, bevor das Fenster vollständig gezeichnet ist, rufen wir vor der Schleife Application.DoEvents auf. (Hinweis: Application.DoEvents nehmen wir weiter unten in diesem Kapitel genauer unter die Lupe.)
In der Praxis können während der Initialisierung Fehler auftreten – beispielsweise wenn mit der Initialisierung ein Datenbank- oder Dateizugriff verbunden ist. Deshalb werden die Initialisierungsarbeiten in einem try-Block ausgeführt. Tritt eine Exception auf, wird der Anwender benachrichtigt und die Anwendung geschlossen. Um die Fehlerbehandlung zu testen, enthält die Schleife des Beispiels eine throw-Anweisung, deren Auskommentierung nur aufgehoben werden muss.
Startet die Anwendung fehlerfrei, muss das Splash-Fenster mit Close geschlossen werden. Dann ist auch der Zeitpunkt gekommen, das Hauptfenster der Anwendung mit Application. Run zu öffnen.
| << zurück |
|
||||||||||||||
|
||||||||||||||
|
||||||||||||||
|
||||||||||||||
Copyright © Galileo Press 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.